package template.security;

import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.apache.log4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.object.MappingSqlQuery;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import template.utils.ReflectionUtils;


/**
 * 使用jdbc来获取登录用户信息。
 * 要注入通过账号查询用户和用户所有的权限的HQL语句。
 * 返回的UserDetails包括了从数据库中获得的完整的信息。
 * 
 * 要想使用这个类来获取用户信息，必须注意转化数据库字段到userdetails对象可能抛出类型转化异常，特别是数字类型的字段
 * 如果特定的jdbc实现类支持udt，那么可以通过注入字段sql类型-java类型的map（sqlRefectionMap）来解决这个问题，否则还是推荐使用HibernateUserDetailsServiceImpl
 * 
 * @author mengyang
 *
 */
public class JdbcUserDetailsServiceImpl extends JdbcDaoSupport implements
		UserDetailsService {
	
	private final Logger logger = Logger.getLogger(getClass());
	
	// 用于查询用户信息的sql语句
	private String usersByUsernameQuery;
	// 用于查询用户所拥有的权限信息的sql语句
	private String authoritiesByUsernameQuery;
	// 实现了UserDetails接口的完整类名
	private Class<? extends UserDetails> userDetailsClass;
	
	private Map<String,Class<?>> sqlRefectionMap;
	private MappingSqlQuery usersByUsernameMapping;
	private MappingSqlQuery authoritiesByUsernameMapping;
	
	@Override
	protected void initDao() throws Exception {
		this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource());
        this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource());
	}

	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		
		UserDetails userDetails = null;
		List userDetailsList = usersByUsernameMapping.execute(username);

		if(userDetailsList != null && userDetailsList.size() > 0){
			final Object target = userDetailsList.get(0);
			
			final List authorityList = authoritiesByUsernameMapping.execute(username);

			//创建代理
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(userDetailsClass);
			enhancer.setCallback(new MethodInterceptor(){
				public Object intercept(Object arg0, Method arg1,
						Object[] arg2, MethodProxy arg3) throws Throwable {
					if(arg1.getName().equals("getAuthorities")){
						return authorityList;
					} else {
						return arg3.invoke(target, arg2);
					}
				}
			});
			
			userDetails = (UserDetails)enhancer.create();
		} else {
			throw new UsernameNotFoundException("没找到账号：'" + username + "'对应的用户信息。");
		}
		
		return userDetails;
	}

	private class UsersByUsernameMapping extends MappingSqlQuery {
        protected UsersByUsernameMapping(DataSource ds) {
            super(ds, usersByUsernameQuery);
            declareParameter(new SqlParameter(Types.VARCHAR));
            compile();
        }

        protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
        	UserDetails userDetails = null;
			try {
				userDetails = userDetailsClass.newInstance();

	            //取得ResultSet的列名 
	            ResultSetMetaData rsmd = rs.getMetaData(); 
	            int columnsCount = rsmd.getColumnCount();
	            for(int i = 1; i <= columnsCount; i++){
	            	ReflectionUtils.setFieldValue(userDetails, rsmd.getColumnLabel(i), rs.getObject(i, sqlRefectionMap));
	            }
			} catch (InstantiationException e1) {
				logger.error("", e1);
			} catch (IllegalAccessException e1) {
				logger.error("", e1);
			}

            return userDetails; 

        }
    }
	
	private class AuthoritiesByUsernameMapping extends MappingSqlQuery {
        protected AuthoritiesByUsernameMapping(DataSource ds) {
            super(ds, authoritiesByUsernameQuery);
            declareParameter(new SqlParameter(Types.VARCHAR));
            compile();
        }

        protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
            String roleName = rs.getString(2);
            GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);

            return authority;
        }
    }
	
	public Map<String, Class<?>> getSqlRefectionMap() {
		return sqlRefectionMap;
	}

	public void setSqlRefectionMap(Map<String, Class<?>> sqlRefectionMap) {
		this.sqlRefectionMap = sqlRefectionMap;
	}

	public String getUsersByUsernameQuery() {
		return usersByUsernameQuery;
	}

	public void setUsersByUsernameQuery(String usersByUsernameQuery) {
		this.usersByUsernameQuery = usersByUsernameQuery;
	}

	public String getAuthoritiesByUsernameQuery() {
		return authoritiesByUsernameQuery;
	}

	public void setAuthoritiesByUsernameQuery(String authoritiesByUsernameQuery) {
		this.authoritiesByUsernameQuery = authoritiesByUsernameQuery;
	}

	public Class<? extends UserDetails> getUserDetailsClass() {
		return userDetailsClass;
	}

	public void setUserDetailsClass(Class<? extends UserDetails> userDetailsClass) {
		this.userDetailsClass = userDetailsClass;
	}

}
